﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Windows;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;

namespace Songhay.Silverlight.Mvvm.ViewModels
{
    using Songhay.Silverlight.Mvvm.Views;
    using Songhay.Mvvm.Models;

    /// <summary>
    /// Base class for all ViewModel classes in the application.
    /// It provides support for property change notifications 
    /// and has a DisplayName property. This class is abstract.
    /// </summary>
    public abstract class ViewModelBase : GalaSoft.MvvmLight.ViewModelBase, INotifyDataErrorInfo
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ViewModelBase"/> class.
        /// </summary>
        /// <remarks>
        /// For details, see
        /// “How to: Implement Property Change Notification”
        /// http://msdn.microsoft.com/en-us/library/ms743695.aspx
        /// 
        /// A sample <c>set</c> block:
        /// <code>
        /// set
        /// {
        ///     _myProperty = value;
        ///     OnPropertyChanged("MyProperty");
        /// }
        /// </code>
        /// </remarks>
        protected ViewModelBase()
        {
            this.SetDefaults();

            //Binding Path:
            Messenger.Default.Register<GenericMessage<CommandDictionaryMessage>>(this,
                message =>
                {
                    this.CommandDictionary.Add(
                        message.Content.DictionaryKey,
                        message.Content.Command);
                });
        }

        #region MVVM Binding properties

        /// <summary>
        /// Gets the MVVM Light Client command.
        /// </summary>
        /// <value>The Client command.</value>
        public RelayCommand<string> ClientCommand {get; protected set;}

        /// <summary>
        /// Returns the user-friendly name of this object.
        /// </summary>
        public string DisplayName
        {
            get { return this._displayName; }
            set
            {
                this._displayName = value;
                this.RaisePropertyChanged("DisplayName");
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this instance has a service error.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has a service error; otherwise, <c>false</c>.
        /// </value>
        public bool IsServiceError
        {
            get { return this._isServiceError; }
            set
            {
                this._isServiceError = value;
                this.RaisePropertyChanged("IsServiceError");
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the view model data valid.
        /// </summary>
        /// <value>
        /// <c>true</c> if the view model data valid; otherwise, <c>false</c>.
        /// </value>
        public bool IsViewModelDataValid
        {
            get { return this._isViewModelDataValid; }
            set
            {
                this._isViewModelDataValid = value;
                base.RaisePropertyChanged("IsViewModelDataValid");
            }
        }

        /// <summary>
        /// Returns the internal name of this object
        /// for retrieval from a collection.
        /// Child classes can set this property to a new value,
        /// or override it to determine the value on-demand.
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets the service error message.
        /// </summary>
        /// <value>The service error message.</value>
        public string ServiceErrorMessage
        {
            get { return this._serviceErrorMessage; }
            set
            {
                this._serviceErrorMessage = value;
                base.RaisePropertyChanged("ServiceErrorMessage");
            }
        }

        #endregion

        #region INotifyDataErrorInfo Members

        /// <summary>
        /// Occurs when the validation errors have changed
        /// for a property or for the entire object.
        /// </summary>
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        /// <summary>
        /// Gets the validation errors
        /// for a specified property or
        /// for the entire object.
        /// </summary>
        /// <param name="propertyName">The name of the property to retrieve validation errors for, or null or <see cref="F:System.String.Empty"/> to retrieve errors for the entire object.</param>
        /// <returns>
        /// The validation errors for the property or object.
        /// </returns>
        public IEnumerable GetErrors(string propertyName)
        {
            if(String.IsNullOrEmpty(propertyName) ||
                !this.ValidationErrors.ContainsKey(propertyName)) return null;
            return this.ValidationErrors[propertyName];
        }

        /// <summary>
        /// Gets a value that indicates whether the object has validation errors.
        /// </summary>
        /// <value></value>
        /// <returns>true if the object currently has validation errors; otherwise, false.</returns>
        public bool HasErrors
        {
            get { return this.ValidationErrors.Count > 0; }
        }

        #endregion

        #region protected properties:

        /// <summary>
        /// Gets or sets the command dictionary.
        /// </summary>
        /// <value>The command dictionary.</value>
        protected Dictionary<string, Action> CommandDictionary { get; set; }

        /// <summary>
        /// Gets the validation errors.
        /// </summary>
        /// <value>The validation errors.</value>
        protected Dictionary<string, List<string>> ValidationErrors
        {
            get
            {
                return _validationErrors;
            }
        }

        /// <summary>
        /// Occurs when one or more of the backing model services fail to load data.
        /// </summary>
        protected event EventHandler ErrorLoading;

        /// <summary>
        /// Occurs when the backing model services successfully load data.
        /// </summary>
        protected event EventHandler LoadComplete;

        /// <summary>
        /// Adds the error.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="error">The error.</param>
        /// <param name="isWarning">if set to <c>true</c> [is warning].</param>
        protected void AddError(object sender, string propertyName, string error, bool isWarning)
        {
            if(!this.ValidationErrors.ContainsKey(propertyName))
                this.ValidationErrors[propertyName] = new List<string>();

            if(!this.ValidationErrors[propertyName].Contains(error))
            {
                if(isWarning) this.ValidationErrors[propertyName].Add(error);
                else this.ValidationErrors[propertyName].Insert(0, error);
                this.RaiseDataErrorsChanged(sender, propertyName);
            }
        }

        /// <summary>
        /// Called when one or more of the backing model services fail to load data.
        /// </summary>
        protected virtual void OnErrorLoading()
        {
            var handler = this.ErrorLoading;
            if(handler != null) handler(this, new EventArgs());
        }

        /// <summary>
        /// Called when one or more of the backing model services loads data.
        /// </summary>
        protected virtual void OnLoadComplete()
        {
            var handler = this.LoadComplete;
            if(handler != null) handler(this, new EventArgs());
        }

        /// <summary>
        /// Removes the error.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="error">The error.</param>
        protected void RemoveError(object sender, string propertyName, string error)
        {
            if(this.ValidationErrors.ContainsKey(propertyName) &&
                this.ValidationErrors[propertyName].Contains(error))
            {
                this.ValidationErrors[propertyName].Remove(error);
                if(this.ValidationErrors[propertyName].Count == 0)
                    this.ValidationErrors.Remove(propertyName);

                this.RaiseDataErrorsChanged(sender, propertyName);
            }
        }

        /// <summary>
        /// Validates the property
        /// with Component Model Data Annotations.
        /// </summary>
        /// <param name="instance">The instance.</param>
        /// <param name="propertyValue">The property value.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="propertyDisplayName">Display name of the property.</param>
        protected void ValidateProperty(object instance, object propertyValue,
            string propertyName, string propertyDisplayName = null)
        {
            try
            {
                Validator.ValidateProperty(propertyValue,
                    new ValidationContext(instance, null, null)
                    {
                        DisplayName = propertyDisplayName ?? propertyName,
                        MemberName = propertyName
                    });

            }
            catch(ValidationException)
            {
                this.IsViewModelDataValid = false;
                throw;
            }
        }

        #endregion

        void RaiseDataErrorsChanged(object sender, string propertyName)
        {
            if(ErrorsChanged != null)
                ErrorsChanged(sender,
                    new DataErrorsChangedEventArgs(propertyName));
        }

        void SetDefaults()
        {
            this._validationErrors = new Dictionary<string, List<string>>();
            this.IsViewModelDataValid = true;

            var commands = new Dictionary<string, Action>();
            commands.Add("#about", () =>
            {
                var about = new AboutChildWindow();
                about.Show();
            });
            this.CommandDictionary = commands;

            this.ClientCommand = new RelayCommand<string>(p =>
            {
                ViewModelBase.DoCommand(p, this.CommandDictionary);
            });
        }

        static void DoCommand(string commandName, Dictionary<string, Action> actionDictionary)
        {
            if(actionDictionary.ContainsKey(commandName))
            {
                var action = actionDictionary[commandName];
                var visual = Application.Current.RootVisual;
                if((action != null) && (visual != null))
                    visual.Dispatcher.BeginInvoke(action);
            }
        }

        string _displayName;
        string _serviceErrorMessage;
        bool _isServiceError;
        bool _isViewModelDataValid;
        Dictionary<string, List<string>> _validationErrors;
    }
}
